package hit.core;

/**
 * Represents a barcode for a {@link Product} or an {@link Item}. 
 * Is able to tell if the Barcode is for a local Item, or for a foreign
 * Product.
 * */
public class Barcode extends HITBase {
	
	/**
	 * The length of a valid barcode.
	 */
	private static final int CODE_LENGTH = 12;
	private static final String LOCAL_START = "4";
	
	private String value;
	
	
	/**
	 * Default Constructor
	 */
	public Barcode() {
		value = "";
	}
	
	/**
	 * Construct a Barcode with the given value. The value must conform to the 
	 * format described in the Barcode class's {@link #isValid(Barcode) isValid(Barcode)} 
	 * method. If it does not conform, an {@link IllegalArgumentException} is thrown.
	 * @param value the numeric String the Barcode should contain
	 * @throws IllegalArgumentException
	 * */
	public Barcode(String value) throws IllegalArgumentException {
		this.value = value;
		
		if (!Barcode.IsValid(this))
			throw new IllegalArgumentException();
	}
	
	/**
	 * Determines whether the Barcode belongs to the local space or 
	 * to the world product barcode space.
	 * @return whether the Barcode is local or foreign
	 * */
	public boolean IsLocal() {
		
		if (value.length() != Barcode.CODE_LENGTH)
		{
			return false;
		}
		
		if (!value.startsWith(Barcode.LOCAL_START))
		{
			return false;
		}
		
		String cds = value.substring(0, value.length() - 1);
		
		int checkDigit = Barcode.GetCheckDigit(cds);
		String temp = value.substring(Barcode.CODE_LENGTH - 1); 
		if (!temp.equals(Integer.toString(checkDigit)))
		{
			return false;
		}
		
		
		return true;
	}
	
	/**
	 * Determines if a Barcode is valid. Checks the length and format of the
	 * underlying Barcode value. <br />
	 * The Barcode is valid if:
	 * <ul>
	 * 	<li>It has length 12</li>
	 * 	<li>It contains only numeric values</li>
	 *  <li>The final character in the value meets the UPC-A Check digit specs</li>
	 * </ul>
	 * @param barcode the Barcode to check
	 * @return whether the Barcode is valid
	 * */
	public static boolean IsValid(Barcode barcode){
		
		return (barcode != null && !barcode.value.equals(""));
	}
	
	/**
	 * @return the underlying String of numbers that the Barcode contains
	 * */
	public String GetValue() {
		return value;
	}

	/**
	 * Sets the value of the Barcode. The value must conform to the format 
	 * described in the Barcode class's {@link #isValid(Barcode) isValid(Barcode)} 
	 * method. If it does not conform, an {@link IllegalArgumentException} is thrown.
	 * @param value the numeric String the Barcode should set
	 * @throws IllegalArgumentException
	 * */
	public void SetValue(String value) throws IllegalArgumentException {
		Barcode testVal = new Barcode(value);
		
		if (!Barcode.IsValid(testVal))
		{
			throw new IllegalArgumentException();
		}
		
		this.value = value;
	}
	
	/***
	 * Returns a new Local Barcode from a given integer value.
	 * @param id The id to use to create the new item. Must be greater than or equal to 0
	 * @return A new Barcode with a valid value to match the new id.
	 * @throws IllegalArgumentException
	 */
	public static Barcode GetLocalBarcode(int id) throws IllegalArgumentException {
		
		if (id < 0)
		{
			throw new IllegalArgumentException();
		}
		
		String temp = Barcode.LOCAL_START;
		temp += String.format("%010d", id);
		
		int check = GetCheckDigit(temp);
		temp += Integer.toString(check);
		
		return new Barcode(temp);
	}
	
	/***
	 * Takes the first 11 characters of a valid barcode and uses it to calculate the check digit.
	 * @param value The first 11 characters of a Barcode.
	 * @return The check digit. -1 if the submitted value is not valid.
	 */
	private static int GetCheckDigit(String value) {
		
		if (value.length() != Barcode.CODE_LENGTH - 1)
			return -1;
		
		int oddValues = 0;
		int evenValues = 0;
		
		// Check each character to be a digit. Also calculate and validate
		//	the check digit.
		for (int i = 0; i < value.length(); i++){
			if (Character.isDigit(value.charAt(i)))
			{
				int temp = Integer.parseInt(Character.toString(value.charAt(i)));
				
				if (i % 2 == 1) // Odd value
				{
					oddValues += temp; 
				}
				else // Even value
				{
					evenValues += temp;
				}
			}
			else
			{
				return -1;
			}
		}
		
		int checkValue = (evenValues * 3 + oddValues) % 10;
		
		if (checkValue != 0)
		{
			checkValue = 10 - checkValue;
		}
		
		return checkValue;
	}
	
	/**
	 * Define two Barcodes to be equal if their value strings are identical
	 * @param o the other Object to test
	 * @return whether the Barcodes are equal or not
	 * */
	@Override
	public boolean equals(Object o) {
		if (!(o instanceof Barcode))
			return false;
		
		//if the value strings weren't the same return false
		Barcode other = (Barcode) o;
		if (!value.equals(other.GetValue()))
			return false;
		
		return true;
	}
	
	/**
	 * Define the hashcode for a Barcode to be equals to the value string's hashcode
	 * @return the hashcode
	 * */
	@Override
	public int hashCode() {
		return value.hashCode();
	}
}
